/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package dataanalysis.main;

import dataanalysis.ASMODE;
import dataanalysis.CLSMODE;
import dataanalysis.Model;
import java.util.Enumeration;
import java.util.Random;
import weka.attributeSelection.ASEvaluation;
import weka.attributeSelection.ASSearch;
import weka.attributeSelection.AttributeSelection;
import weka.attributeSelection.AttributeTransformer;
import weka.attributeSelection.semiAS.SemiAttributeSelector;
import weka.classifiers.Classifier;
import weka.classifiers.CostMatrix;
import weka.classifiers.Evaluation;
import weka.classifiers.collective.CollectiveClassifier;
import weka.core.Attribute;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.OptionHandler;
import weka.core.Utils;
import weka.filters.Filter;

/**
 * 
 * @author Administrator
 */
public final class DataProcesser {

    private String m_strClassifierResult = null;
    private Instances m_selectData = null;
    private Instances m_filterData = null;
    private String m_strASResult = null;
    private String m_strPredictResult = null;

    /**
     * 对data进行预处理
     * @param data 输入参数，输入需要进行预处理的数据，将其引用值修改为预处理后的新数据
     * @param filter 输入参数，指定预处理方法
     * @throws Exception
     */
    public void applyFilter(final Instances data, final Filter filter) throws Exception {
        if (data == null || filter == null) {
            return;
        }
        Instances copy = new Instances(data);
        filter.setInputFormat(copy);
        Instances newInstances = Filter.useFilter(copy, filter);
        if (newInstances == null || newInstances.numAttributes() < 1) {
            throw new Exception("Dataset is empty.");
        }
        //addUndoPoint();
        if (m_filterData != null) {
            m_filterData.delete();
        }
        m_filterData = newInstances;
    }

    /**
     * 对data进行有监督特征选择
     * @param data 输入参数，输入需要进行有监督特征选择的数据
     * @param evaluator 输入参数，指定评价方法
     * @param searcher 输入参数，指定搜索策略
     * @param ASMode 输入参数，指定特征选择模式
     * @param numFolds 输入参数，指定十字交叉次数
     * @param seed 输入参数，给定随机种子
     * @throws Exception
     */
    public void startSupAttributeSelection(final Instances data, final ASEvaluation evaluator, final ASSearch searcher, final ASMODE ASMode, final int numFolds, final int seed) throws Exception {
        if (data == null || evaluator == null || searcher == null) {
            return;
        }
        // Copy the current state of things
        Instances inst = new Instances(data);

        StringBuffer outBuff = new StringBuffer();
        String sname = searcher.getClass().getName();
        String ename = evaluator.getClass().getName();
        AttributeSelection eval = null;

        // Output some header information
        outBuff.append("Evaluator:    " + ename);
        if (evaluator instanceof OptionHandler) {
            String[] o = ((OptionHandler) evaluator).getOptions();
            outBuff.append(" " + Utils.joinOptions(o));
        }
        outBuff.append("\nSearch:       " + sname);
        if (searcher instanceof OptionHandler) {
            String[] o = ((OptionHandler) searcher).getOptions();
            outBuff.append(" " + Utils.joinOptions(o));
        }
        outBuff.append("\n");
        outBuff.append("Relation:     " + inst.relationName() + '\n');
        outBuff.append("Instances:    " + inst.numInstances() + '\n');
        outBuff.append("Attributes:   " + inst.numAttributes() + '\n');
        if (inst.numAttributes() < 100) {
            for (int i = 0; i < inst.numAttributes(); i++) {
                outBuff.append("              " + inst.attribute(i).name() + '\n');
            }
        } else {
            outBuff.append("              [list of attributes omitted]\n");
        }
        outBuff.append("Evaluation mode:    ");
        switch (ASMode) {
            case FULL: // select using all training
                outBuff.append("evaluate on all training data\n");
                break;
            case CROSS: // CV mode
                outBuff.append("" + numFolds + "-fold cross-validation\n");
                break;
        }
        outBuff.append("\n");

        eval = new AttributeSelection();
        eval.setEvaluator(evaluator);
        eval.setSearch(searcher);
        eval.setFolds(numFolds);
        eval.setSeed(seed);
        if (ASMode == ASMODE.CROSS) {
            eval.setXval(true);
        }

        switch (ASMode) {
            case FULL: // select using training
                eval.SelectAttributes(inst);
                break;

            case CROSS: // CV mode
                Random random = new Random(seed);
                inst.randomize(random);
                if (inst.attribute(inst.classIndex()).isNominal()) {
                    inst.stratify(numFolds);
                }
                for (int fold = 0; fold < numFolds; fold++) {
                    Instances train = inst.trainCV(numFolds, fold, random);

                    eval.selectAttributesCVSplit(train);
                }
                break;
            default:
                throw new Exception("Test mode not implemented");
        }
        if (ASMode == ASMODE.FULL) {
            outBuff.append(eval.toResultsString());
        } else {
            outBuff.append(eval.CVResultsString());
        }
        outBuff.append("\n");

        if (evaluator instanceof AttributeTransformer) {
            Instances transformed = ((AttributeTransformer) evaluator).transformedData();
            transformed.setRelationName("AT: " + transformed.relationName());
            if (m_selectData != null) {
                m_selectData.delete();
            }
            m_selectData = transformed;
        } else if (ASMode == ASMODE.FULL) {
            Instances reducedInst = eval.reduceDimensionality(inst);
            if (m_selectData != null) {
                m_selectData.delete();
            }
            m_selectData = reducedInst;
        }
        m_strASResult = outBuff.toString();
    }

    /**
     * 用data为数据集训练分类器classifier
     * @param data 输入参数，指定数据集
     * @param classifier 输入输出参数，指定分类器方法，输出经过训练的分类器
     * @param testMode 输入参数，指定训练评价模式
     * @param numFolds 输入参数，指定十字交叉次数
     * @param percent 输入参数，指定训练集抽样比率
     * @param seed 输入参数，指定随机抽样种子
     * @throws Exception
     */
    public void startClassifier(final Instances data, final Classifier classifier, final CLSMODE testMode, final int numFolds, final double percent, final int seed) throws Exception {
        if (data == null || classifier == null) {
            return;
        }
        // Copy the current state of things
        CostMatrix costMatrix = null;
        Instances inst = new Instances(data);
        // for timing
        long trainTimeStart = 0, trainTimeElapsed = 0;
        Classifier template = Classifier.makeCopy(classifier);
        StringBuffer outBuff = new StringBuffer();
        String cname = classifier.getClass().getName();
        Evaluation eval = null;
        // Output some header information
        outBuff.append("Scheme:       " + cname);
        if (classifier instanceof OptionHandler) {
            String[] o = ((OptionHandler) classifier).getOptions();
            outBuff.append(" " + Utils.joinOptions(o));
        }
        outBuff.append("\n");
        outBuff.append("Relation:     " + inst.relationName() + '\n');
        outBuff.append("Instances:    " + inst.numInstances() + '\n');
        outBuff.append("Attributes:   " + inst.numAttributes() + '\n');
        if (inst.numAttributes() < 100) {
            for (int i = 0; i < inst.numAttributes(); i++) {
                outBuff.append("              " + inst.attribute(i).name() + '\n');
            }
        } else {
            outBuff.append("              [list of attributes omitted]\n");
        }

        outBuff.append("Test mode:    ");
        switch (testMode) {
            case SELFTRAIN: // Test on training
                outBuff.append("evaluate on training data\n");
                break;
            case CROSS: // CV mode
                outBuff.append("" + numFolds + "-fold cross-validation\n");
                break;
            case PERCENT: // Percent split
                outBuff.append("split " + percent + "% train, remainder test\n");
                break;

        }
        if (costMatrix != null) {
            outBuff.append("Evaluation cost matrix:\n").append(costMatrix.toString()).append("\n");
        }
        outBuff.append("\n");

        // Build the model and output it.
        trainTimeStart = System.currentTimeMillis();
        classifier.buildClassifier(inst);
        trainTimeElapsed = System.currentTimeMillis() - trainTimeStart;

        outBuff.append("=== Classifier model (full training set) ===\n\n");
        outBuff.append(classifier.toString() + "\n");
        outBuff.append("\nTime taken to build model: "
                + Utils.doubleToString(trainTimeElapsed / 1000.0, 2) + " seconds\n\n");
        switch (testMode) {
            case SELFTRAIN: // Test on training
                eval = new Evaluation(inst, costMatrix);
                for (int jj = 0; jj < inst.numInstances(); jj++) {
                    eval.evaluateModelOnceAndRecordPrediction(classifier, inst.instance(jj));
                }
                outBuff.append("=== Evaluation on training set ===\n");
                break;
            case CROSS: // CV mode
                Random random = new Random(seed);
                inst.randomize(random);
                if (inst.attribute(inst.classIndex()).isNominal()) {
                    inst.stratify(numFolds);
                }
                eval = new Evaluation(inst, costMatrix);
                // Make some splits and do a CV
                for (int fold = 0; fold < numFolds; fold++) {
                    Instances train = inst.trainCV(numFolds, fold, random);
                    eval.setPriors(train);
                    Classifier current = null;
                    current = Classifier.makeCopy(template);
                    current.buildClassifier(train);
                    Instances test = inst.testCV(numFolds, fold);
                    for (int jj = 0; jj < test.numInstances(); jj++) {
                        eval.evaluateModelOnceAndRecordPrediction(current, test.instance(jj));
                    }
                }
                if (inst.attribute(inst.classIndex()).isNominal()) {
                    outBuff.append("=== Stratified cross-validation ===\n");
                } else {
                    outBuff.append("=== Cross-validation ===\n");
                }
                break;

            case PERCENT: // Percent split
                // merge into train and test sets
                Instances train = new Instances(inst, inst.numInstances());
                Instances test = new Instances(inst, inst.numInstances());
                //随即生成，并且能基本保证没类都有数据
                Random rand = new Random(seed);
                inst.randomize(rand);
                generateRandTrainTestSet(inst, train, test, percent, rand);
                Classifier current = null;
                current = Classifier.makeCopy(template);
                //进行了修改
                current.buildClassifier(train);
                eval = new Evaluation(train, costMatrix);
                if (current instanceof CollectiveClassifier) {
                    CollectiveClassifier c = (CollectiveClassifier) current;
                    c.reset();
                    c.setTestSet(test);
                }
                //===============================================
                for (int jj = 0; jj < test.numInstances(); jj++) {

                    eval.evaluateModelOnceAndRecordPrediction(current, test.instance(jj));
                }
                outBuff.append("=== Evaluation on test split ===\n");
                break;

            default:
                throw new Exception("Test mode not implemented");
        }
        outBuff.append(eval.toSummaryString(false) + "\n");
        if (inst.attribute(inst.classIndex()).isNominal()) {
            outBuff.append(eval.toClassDetailsString() + "\n");
            outBuff.append(eval.toMatrixString() + "\n");
        }
        m_strClassifierResult = outBuff.toString();
    }

    /**
     * 采用训练好的分类器模型classifier对data数据集进行类别预测
     * @param data 输入参数，指定待预测的数据集
     * @param model 输入参数，指定预测模型
     * @throws Exception
     */
    public void startPredictByModel(final Instances data, final Model model) throws Exception {
        if (data == null || model == null) {
            return;
        }
        Instances inst = new Instances(data);
        StringBuffer outBuff = new StringBuffer();

        Instances trainHeader = model.getDataHeader();
        Classifier classifier = model.getClassifier();
        if (trainHeader != null) {
            if (trainHeader.classIndex() > inst.numAttributes() - 1) {
                throw new Exception("Train and test set are not compatible");
            }
            inst.setClassIndex(trainHeader.classIndex());
            if (!trainHeader.equalHeaders(inst)) {
                throw new Exception("Train and test set are not compatible");
            }
        }
//        else {
//            inst.setClassIndex(inst.numAttributes() - 1);
//        }

//        inst.setClassIndex(inst.numAttributes() - 1);

        CostMatrix costMatrix = null;
        Evaluation eval = new Evaluation(inst, costMatrix);
        eval.useNoPriors();

        outBuff.append("\n=== Re-evaluation on test set ===\n\n");
        outBuff.append("User supplied test set\n");
        outBuff.append("Relation:     " + inst.relationName() + '\n');
        outBuff.append("Instances:    " + inst.numInstances() + "\n");
        outBuff.append("Attributes:   " + inst.numAttributes() + "\n\n");
        if (trainHeader == null) {
            outBuff.append("NOTE - if test set is not compatible then results are " + "unpredictable\n\n");
        }

        printPredictionsHeader(outBuff, inst, "open data");

        for (int jj = 0; jj < inst.numInstances(); jj++) {

            eval.evaluateModelOnceAndRecordPrediction(classifier, inst.instance(jj));
            outBuff.append(predictionText(classifier, inst.instance(jj), jj + 1));
        }

        outBuff.append(eval.toSummaryString(true) + "\n");

        if (inst.classAttribute().isNominal()) {
            outBuff.append(eval.toClassDetailsString() + "\n");
            outBuff.append(eval.toMatrixString() + "\n");
        }

        m_strPredictResult = outBuff.toString();
    }

    public void startSemiAttributeSelection(final Instances data, final SemiAttributeSelector semiSelector) throws Exception {
        if (data == null || semiSelector == null) {
            return;
        }
        Instances inst = new Instances(data);
        StringBuffer outBuff = new StringBuffer();
        String sname = semiSelector.getClass().getName();
        // Output some header information
        outBuff.append("Selector:    " + sname);
//        if (selector instanceof OptionHandler) {
//            String[] o = ((OptionHandler) selector).getOptions();
//            outBuff.append(" " + Utils.joinOptions(o));
//        }
        outBuff.append("\n");
        outBuff.append("Relation:     " + inst.relationName() + '\n');
        outBuff.append("Instances:    " + inst.numInstances() + '\n');
        outBuff.append("Attributes:   " + inst.numAttributes() + '\n');
        if (inst.numAttributes() < 100) {
            for (int i = 0; i < inst.numAttributes(); i++) {
                outBuff.append("              " + inst.attribute(i).name() + '\n');
            }
        } else {
            outBuff.append("              [list of attributes omitted]\n");
        }
        semiSelector.buildSelector(inst);
        semiSelector.startSelector();
        Instances reducedInst = semiSelector.reduceDimensionality(inst);
        outBuff.append(semiSelector.toString());
        if (m_selectData != null) {
            m_selectData.delete();
        }
        m_selectData = reducedInst;

        m_strASResult = outBuff.toString();
    }

    private static String predictionText(Classifier classifier, Instance inst, int instNum) throws Exception {

        //> inst#   actual   predicted   error  probability distribution

        StringBuffer text = new StringBuffer();
        // inst #
        text.append(Utils.padLeft("" + instNum, 6) + " ");
        if (inst.classAttribute().isNominal()) {

            // actual
            if (inst.classIsMissing()) {
                text.append(Utils.padLeft("?", 10) + " ");
            } else {
                text.append(Utils.padLeft("" + ((int) inst.classValue() + 1) + ":" + inst.stringValue(inst.classAttribute()), 10) + " ");
            }

            // predicted
            double[] probdist = null;
            double pred;
            if (inst.classAttribute().isNominal()) {
                probdist = classifier.distributionForInstance(inst);
                pred = (double) Utils.maxIndex(probdist);
                if (probdist[(int) pred] <= 0.0) {
                    pred = Instance.missingValue();
                }
            } else {
                pred = classifier.classifyInstance(inst);
            }
            text.append(Utils.padLeft((Instance.isMissingValue(pred) ? "?" : (((int) pred + 1) + ":" + inst.classAttribute().value((int) pred))), 10) + " ");
            // error
            if (pred == inst.classValue()) {
                text.append(Utils.padLeft(" ", 6) + " ");
            } else {
                text.append(Utils.padLeft("+", 6) + " ");
            }

            // prob dist
            if (inst.classAttribute().type() == Attribute.NOMINAL) {
                for (int i = 0; i < probdist.length; i++) {
                    if (i == (int) pred) {
                        text.append(" *");
                    } else {
                        text.append("  ");
                    }
                    text.append(Utils.doubleToString(probdist[i], 5, 3));
                }
            }
        } else {

            // actual
            if (inst.classIsMissing()) {
                text.append(Utils.padLeft("?", 10) + " ");
            } else {
                text.append(Utils.doubleToString(inst.classValue(), 10, 3) + " ");
            }

            // predicted
            double pred = classifier.classifyInstance(inst);
            if (Instance.isMissingValue(pred)) {
                text.append(Utils.padLeft("?", 10) + " ");
            } else {
                text.append(Utils.doubleToString(pred, 10, 3) + " ");
            }

            // err
            if (!inst.classIsMissing() && !Instance.isMissingValue(pred)) {
                text.append(Utils.doubleToString(pred - inst.classValue(), 10, 3));
            }
        }

        // additional Attributes
        text.append(" (");
        boolean first = true;
        for (int i = 0; i < inst.numAttributes() - 1; i++) {
            if (!first) {
                text.append(",");
            } else {
                first = false;
            }
            text.append(inst.toString(i));
        }
        text.append(")");

        text.append("\n");
        return text.toString();
    }

    private static void printPredictionsHeader(StringBuffer outBuff, Instances inst, String title) {
        outBuff.append("=== Predictions on " + title + " ===\n\n");
        outBuff.append(" inst#,    actual, predicted, error");
        if (inst.classAttribute().isNominal()) {
            outBuff.append(", probability distribution");
        }
        outBuff.append(" (");
        boolean first = true;
        for (int i = 0; i < inst.numAttributes() - 1; i++) {
            if (!first) {
                outBuff.append(",");
            } else {
                first = false;
            }
            outBuff.append(inst.attribute(i).name());
        }
        outBuff.append(")");
        outBuff.append("\n");
    }

    private static void generateRandTrainTestSet(Instances runInstances, Instances train, Instances test, double percent, Random rand) {
        // Nominal class
        if (runInstances.classAttribute().isNominal()) {

            // create the subset for each classs
            int numClasses = runInstances.numClasses();
            Instances[] subsets = new Instances[numClasses + 1];
            for (int i = 0; i < numClasses + 1; i++) {
                subsets[i] = new Instances(runInstances, 10);
            }

            // divide instances into subsets
            Enumeration e = runInstances.enumerateInstances();
            while (e.hasMoreElements()) {
                Instance inst = (Instance) e.nextElement();
                if (inst.classIsMissing()) {
                    subsets[numClasses].add(inst);
                } else {
                    subsets[(int) inst.classValue()].add(inst);
                }
            }

            // Compactify them
            for (int i = 0; i < numClasses + 1; i++) {
                subsets[i].compactify();
            }


            for (int i = 0; i < numClasses + 1; i++) {
                int trainSize =
                        Utils.probRound(subsets[i].numInstances() * percent / 100, rand);
                for (int j = 0; j < trainSize; j++) {
                    train.add(subsets[i].instance(j));
                }
                for (int j = trainSize; j < subsets[i].numInstances(); j++) {
                    test.add(subsets[i].instance(j));
                }
                // free memory
                subsets[i] = null;
            }
            train.compactify();
            test.compactify();

            // randomize the final sets
            train.randomize(rand);
            test.randomize(rand);
        } else {

            // Numeric target
            int trainSize =
                    Utils.probRound(runInstances.numInstances() * percent / 100, rand);
            int testSize = runInstances.numInstances() - trainSize;
            train = new Instances(runInstances, 0, trainSize);
            test = new Instances(runInstances, trainSize, testSize);
        }
    }

    public Instances getFilterData() {
        return m_filterData;
    }

    public Instances getSelectData() {
        return m_selectData;
    }

    public String getASResultTxt() {
        return m_strASResult;
    }

    public String getClassifierResultTxt() {
        return m_strClassifierResult;
    }

    public String getPredictResultTxt() {
        return m_strPredictResult;
    }
}
